//------------------------------------------------------------------------------
// <copyright company="Telligent Systems">
//     Copyright (c) Telligent Systems Corporation.  All rights reserved.
// </copyright> 
//------------------------------------------------------------------------------

using System;
using System.Net;
using System.Text.RegularExpressions;
using System.Xml;
using System.Collections;
using CommunityServer.Components;
using CommunityServer.Reader.Components;
using CommunityServer.Configuration;
using CommunityServer.Blogs.Components;
using Rss;

namespace CommunityServer.RollerBlogs.Components
{

	/// <summary>
    /// Summary description for RollerBlogUpdater.
	/// </summary>
	public class RollerBlogUpdater : IJob
	{
		private const string CATEGORY_STRING = "RollerBlogUpdater";
		
        #region IJob Members

        public void Execute(XmlNode node)
        {
            SiteSettingsManager.IterateSiteSettings(new SiteSettingsListIterator(UpdateBlogRollPerSettingsID));
        }

        void UpdateBlogRollPerSettingsID(int settingsid)
        {
            CSContext.Create(settingsid);
            ArrayList rollerBlogFeeds = RollerBlogFeeds.GetAllOutOfDateEnabledFeeds();
            this.UpdateAllFeeds(rollerBlogFeeds);
        }

		#endregion

		#region Public Methods

		private void UpdateAllFeeds(ArrayList feeds)
		{
			// Now, go through each, grab the RSS feed and update the database.
			for (int i = 0; i < feeds.Count; i++)
			{
                RollerBlogFeed feed = (RollerBlogFeed)feeds[i];
				UpdateFeed(feed);
			}			
		}


        private void UpdateFeed(RollerBlogFeed feed)
		{
			RssFeed rssFeed = null;
            FeedState state = FeedState.UpdateSuccessful;

			try
			{
				rssFeed = RssFeed.Read(feed.CreateRequest());	

				// RSS.NET will return cached if it's unable to load the current feed (any WebException). We
				// want to show that as cache in order to show the feed state in the reader UI.
				if (rssFeed.Cached) 
                    state = FeedState.FeedCached;
			}
			catch (WebException wexc)
			{
				if ( wexc.Message.IndexOf( "(404)" ) > 0 )
				{
					EventLogs.Warn( String.Format( "404 Error Feed: {0} Section: {1}", feed.SiteUrl, feed.SectionId ), CATEGORY_STRING, 413 );
                
					state = FeedState.FeedNotFound;
				}
				else if ( wexc.Message.IndexOf( "(304)" ) > 0 )
				{
					state = FeedState.FeedCached;
				}
				else
				{
					EventLogs.Warn( String.Format( "Unknown error on Feed.CreateRequest() Message: {0} Feed: {1} Section: {2}", wexc.Message, feed.SiteUrl, feed.SectionId ), CATEGORY_STRING, 413 );

					state = FeedState.UnknownError;
				}
			}
			catch (XmlException xmlEx)
			{
				EventLogs.Warn( String.Format( "Invalid Format Feed.CreateRequest() Xml Exception Message: {0} Feed: {1} Section: {2}", xmlEx.Message, feed.SiteUrl, feed.SectionId ), CATEGORY_STRING, 413 );
				state = FeedState.InvalidFormat;
			}
			catch (Exception ex )
			{
				EventLogs.Warn( String.Format( "General Error Feed.CreateRequest() Xml Exception Message: {0} Feed: {1} Section: {2}", ex.Message, feed.SiteUrl, feed.SectionId ), CATEGORY_STRING, 413 );
				state = FeedState.UnknownError;
			}

			// If the feed state is anything bad, let's update the feed state only, and continue.
			// Otherwise, attempt to update the feed.
            if (state != FeedState.UpdateSuccessful)
            {
                feed.State = state;
                feed.LastUpdateDate = DateTime.Now;
                RollerBlogFeeds.UpdateFeed(feed);
            }
            else
            {
			    ProcessFeedItems(rssFeed, feed);
			}			
		}

        private void ProcessFeedItems(RssFeed rssFeed, RollerBlogFeed feed)
        {
            // Pull back a chunk of current posts for this feed and blog (sectionID).
            // Once we pull back the chunk we put it in a hashtable so we can quickly
            // see if a post is already present based on its PermaLink.  RSS is such a lose standard
            // and basically nothing is required.  Usually the only sure bet for something
            // existing in each and every feed is the PermaLink, and even sometimes this is not
            // present.
            ArrayList fRollerPosts = RollerBlogsDataProvider.Instance().GetRollerBlogPosts(feed);
            Hashtable feedRollerPosts = new Hashtable();

			for ( int i = 0 ; i < fRollerPosts.Count ; i++ )
			{
				RollerBlogPost post = (RollerBlogPost) fRollerPosts[i];

				if ( false == feedRollerPosts.ContainsKey( post.PermaLink ) )
				{
					feedRollerPosts.Add( post.PermaLink, post );
				}
				else
				{
					EventLogs.Warn( string.Format( "Error Processing Feed: {0} SectionID: {2} PostID {1}; The database contains duplicate permalinks... {2}", feed.UrlId, post.PostID, feed.SectionId ), CATEGORY_STRING, 413 );
				}
			}
            
            // If the current feed has no channels, continue onto the next.
            if (rssFeed.Channels == null || rssFeed.Channels.Count < 1) return;

            // Grab the first channel (most RSS feeds never implement multiple channels)
            RssChannel channel = rssFeed.Channels[0];

            feed.LastUpdateDate = DateTime.Now;
            RollerBlogFeeds.UpdateFeed(feed);
            try
            {
                User user = Users.GetAnonymousUser(true);

                foreach (RssItem item in channel.Items)
                {
                    //  We need to make sure that we haven't already created a post for the item.
                    //  We make this check based on PermaLink because this is the only attribute
                    //  that exists most of the time.  Please note that in the RSS standard it's
                    //  almost impossible to guarantee that a particular attribute will be defined
                    //  in a feed.
                    if (item.Link != null && item.Link.ToString() != "")
                    {
                        if (!feedRollerPosts.ContainsKey(item.Link.ToString()))
                        {
                            WeblogPost post = new WeblogPost();
							
							// Set Default Values
							Weblog weblog = Weblogs.GetWeblog(feed.SectionId, true);
							post.PostConfig = weblog.DefaultPostConfig;
							post.EnableRatings = weblog.EnableRatingsDefault;
							post.EnableTrackBacks = weblog.EnableTrackbacksDefault;
							post.EnableAllOwnerNotification = weblog.EnableAllOwnerNotification;
							post.FeedbackNotificationType = weblog.FeedbackNotificationType;
							post.ModerationType = weblog.ModerationTypeDefault;
                            
                            post.IsExternal = true;

                            post.SectionID = feed.SectionId;
                            post.ParentID = 0;
                            post.BlogPostType = BlogPostType.Post;
                            post.PostID = -1;
                            post.TitleUrl = item.Link.ToString();
                            
                            post.Subject = item.Title;
                            post.ExcerptSize = feed.ExerptSize;
                            post.DisplayExcerpt = !feed.PostFullText;

                            post.Body = item.Description;

                            post.PostType = PostContentType.HTML;

                            //ToDo: rethink this. Do we want to associate external feeds with their blog?
                            post.SubmittedUserName = channel.Title;
                            post.Username = channel.Title;
                            post.SetExtendedAttribute("channel_link", channel.Link.ToString());

                            post.FormattedBody = null;
							
							//bad pub dates in rss feed need to be corrected.
							//YES that is a Less than.  One feed was returning a date LESS THAN DateTime.MinValue!!
							if ( item.PubDate.Ticks <= DateTime.MinValue.Ticks )
							{
								post.PostDate = DateTime.Now;
								post.UserTime = DateTime.Now.ToLocalTime();
							}
							else
							{
								post.PostDate = item.PubDate.ToLocalTime();
								post.UserTime = item.PubDate;
							}
                            post.IsAggregated = feed.IsBlogAggregated;
                            post.IsCommunityAggregated = feed.IsBlogRollAggregated;
                            post.SyndicateExcerpt = true;

                            if(item.Categories != null && item.Categories.Count > 0)
                            {
                                ArrayList list = TaggerExtractor.GetTags(item.Description);
                                for(int i = 0; i < item.Categories.Count; i++)
                                {
                                    list.Add(item.Categories[i].Name);
                                }

                                post.Categories = list.ToArray(typeof(string)) as string[];
                            }

                            //User user = Users.GetUser(0, "Anonymous", true, true);
                            int postID = -1;

                            // Now lets add the post to cs_posts
                            WeblogPosts.Add(post, user, out postID);

                            if (postID > 0) //if not then we had a problem adding the WeblogPost, so dont save it
                            {
                                // Now we need to update cs_RoolerBlogPost with this post information
                                RollerBlogPost rbp = new RollerBlogPost();
                                rbp.PostID = postID;
                                rbp.PermaLink = item.Link.ToString();
                                rbp.CommentsUrl = item.Comments;

                                try { rbp.CommentCount = item.CommentCount; }
                                catch { }

                                if (item.Guid != null)
                                {
                                    rbp.GuidName = item.Guid.Name;
                                    if ((item.Guid.PermaLink.ToString() == "DBBool.Null") || (item.Guid.PermaLink.ToString() == "DBBool.False"))
                                        rbp.GuidIsPermaLink = false;
                                    else
                                        rbp.GuidIsPermaLink = true;
                                }
                                else
                                {
                                    if (item.Link != null)
                                        rbp.GuidName = item.Link.ToString();
                                    else
                                        rbp.GuidName = "Not Provided";
                                }
                                rbp.SectionId = feed.SectionId;
                                rbp.UrlId = feed.UrlId;
                                RollerBlogPosts.AddPost(rbp);
                            }
                        }
                        else //lets update the remote comment count
                        {
                            RollerBlogPost rbp = (RollerBlogPost)feedRollerPosts[item.Link.ToString()];
                            
                            try { rbp.CommentCount = item.CommentCount; }
                            catch { }

                            RollerBlogPosts.UpdatePost(rbp);
                        }
                    }
                }
            }

            catch (Exception ex)
            {
				EventLogs.Warn( String.Format( "Could not update feed items Feed: {0} Section: {1} Error Message: {2} Stack Trace: {3}", feed.SiteUrl, feed.SectionId, ex.Message, ex.StackTrace )
					, CATEGORY_STRING, 413 );
                feed.State = FeedState.UnknownError;
                RollerBlogFeeds.UpdateFeed(feed);
            }
        }

	    #endregion

        private class TaggerExtractor
        {
            static Regex linkRegex = new Regex( "<a[^>]*>(.*?)</a>",RegexOptions.IgnoreCase | RegexOptions.Compiled | RegexOptions.Multiline);
            static Regex valueRegex = new Regex( ">(.+)</a>",RegexOptions.IgnoreCase | RegexOptions.Compiled | RegexOptions.Multiline);
            static Regex validRegex = new Regex( @"rel\s*\=\s*""tag"">",RegexOptions.IgnoreCase | RegexOptions.Compiled | RegexOptions.Multiline);

            public static ArrayList GetTags(string text)
            {
                ArrayList list = new ArrayList();

                if(!Globals.IsNullorEmpty(text))
                {
                    foreach(Match m in linkRegex.Matches(text))
                    {
                        if(validRegex.IsMatch(m.Value))
                        {
                            foreach(Match m2 in valueRegex.Matches(m.Value))
                            {
                                string tag = m2.Value;
                                list.Add(tag.Substring(1,tag.Length -5));
                            }
                        }
                    }
                }
                return list;
            }
        }
	}
}